/**
 * 
 */
package pt.inescid.components.policyengine;

import java.util.Vector;

import pt.inescid.components.api.Component;
import pt.inescid.components.api.PolicyEngineI;
import pt.inescid.components.messages.AvailabilityMsg;
import pt.inescid.components.messages.Task;
import pt.inescid.components.messages.TaskResult;
import pt.inescid.components.messages.UpdateMsg;
import pt.inescid.components.overlaymanager.NodeMetrics;
import pt.inescid.components.policyengine.heimdall.base.EventField;
import pt.inescid.components.policyengine.heimdall.base.EventObj;
import pt.inescid.components.policyengine.heimdall.base.User;
import pt.inescid.components.policyengine.heimdall.commons.TimeUtilities;
import pt.inescid.components.policyengine.heimdall.core.HeimdallServer;
import pt.inescid.components.policyengine.heimdall.core.MainConsole;
import pt.inescid.components.policyengine.heimdall.pap.base.CompositeDefinition;
import pt.inescid.components.policyengine.heimdall.pap.base.EventFieldDefinition;
import pt.inescid.components.policyengine.heimdall.pap.base.ModelDefinition;
import pt.inescid.components.policyengine.heimdall.pap.base.PolicyDefinition;
import pt.inescid.components.policyengine.heimdall.pap.base.RuleDefinition;
import pt.inescid.core.GridP2P;
import pt.inescid.utils.Log;
import rice.p2p.commonapi.Message;
import rice.p2p.commonapi.rawserialization.RawMessage;

/**
 * @author Sergio Esteves (sesteves at gsd.inesc-id.pt)
 *
 */
public class PolicyEngine extends Component implements PolicyEngineI {
	
	private User u0, u1;
	
	private HeimdallServer server1;
	
	private int lboundHour, uboundHour;
	
	private int maxConcurrentTasks;
	
	private boolean isIdle;
	
	// minimum ratio required for a remote node to perform work in this machine
	private double minRatio;
	
	/*
	 * inwork = work performed in this machine; 
	 * outwork = work performed outside;
	 * ratio = inwork/outwork
	 */
	private double inwork = 1, outwork = 1, ratio = 1;
	
	
	public PolicyEngine(GridP2P gridP2P, int lboundHour, int uboundHour, int maxConcurrentTasks, boolean isIdle, double minRatio) {
		super(gridP2P);
		this.lboundHour = lboundHour;
		this.uboundHour = uboundHour;
		this.maxConcurrentTasks = maxConcurrentTasks;
		this.isIdle = isIdle;
		this.minRatio = minRatio;
	}
	
	public void init() {
		initPolicies();
		server1 = HeimdallServer.getInstance();	
		log.write(this, "Policy Engine initialized", Log.FINER);
	}
	
	private void initPolicies() {		
		u0 = new User(0,"u0");
		
		ModelDefinition model = new ModelDefinition(MainConsole.MODEL_TAG);		
		PolicyDefinition newPolicy = new PolicyDefinition("test");
		model.addPolicy(newPolicy);
		u0.addModel(model);
		// policy for the time interval to perform work
		CompositeDefinition rdAND = new CompositeDefinition(RuleDefinition.LOGICAL_AND_OPERATOR, null);
		
		RuleDefinition rd1 = new EventFieldDefinition(model, RuleDefinition.EVENT_FIELD_EXPRESSION,
				"int", null, ".1");		
		RuleDefinition rd2 = new EventFieldDefinition(model, RuleDefinition.EVENT_FIELD_EXPRESSION,
				"int", null, ".2");
		RuleDefinition rd3 = new EventFieldDefinition(model, RuleDefinition.EVENT_FIELD_EXPRESSION,
				"int", null, ".3");
		
		CompositeDefinition rdOR = new CompositeDefinition(RuleDefinition.LOGICAL_OR_OPERATOR, null); 			
		CompositeDefinition rdEQS = new CompositeDefinition(RuleDefinition.EQUALS_OPERATOR, null);
		rdEQS.addOperand(rd3);
		rdEQS.addOperand(rd1);
		CompositeDefinition rdGTO = new CompositeDefinition(RuleDefinition.GREATER_THAN_OPERATOR, null);		
		rdGTO.addOperand(rd3);
		rdGTO.addOperand(rd1);
		rdOR.addOperand(rdGTO);
		rdOR.addOperand(rdEQS);
		
		CompositeDefinition rdLTO = new CompositeDefinition(RuleDefinition.LESSER_THAN_OPERATOR, null);		
		rdLTO.addOperand(rd3);
		rdLTO.addOperand(rd2);
		
		rdAND.addOperand(rdOR);
		rdAND.addOperand(rdLTO);

		newPolicy.addExpression(rdAND, null, 0);	
		newPolicy.setAuthorization(rdAND);
		
		// policy for the maximum concurrent tasks
		PolicyDefinition newPolicy2 = new PolicyDefinition("maxConcTasks");
		RuleDefinition rd21 = new EventFieldDefinition(model, RuleDefinition.EVENT_FIELD_EXPRESSION,
				"int", null, ".21"); // maxConcurrentTasks
		RuleDefinition rd22 = new EventFieldDefinition(model, RuleDefinition.EVENT_FIELD_EXPRESSION,
				"int", null, ".22"); // tasks running
		CompositeDefinition rd2LTO = new CompositeDefinition(RuleDefinition.LESSER_THAN_OPERATOR, null);
		rd2LTO.addOperand(rd22);
		rd2LTO.addOperand(rd21);
		
		model.addPolicy(newPolicy2);
		newPolicy2.addExpression(rd2LTO, null, 0);
		newPolicy2.setAuthorization(rd2LTO);
		
		// policy for the ratio of a node		
		PolicyDefinition newPolicy3 = new PolicyDefinition("ratio");
		ModelDefinition model1 = new ModelDefinition(MainConsole.MODEL_TAG);
		model1.addPolicy(newPolicy3);
		RuleDefinition rd31 = new EventFieldDefinition(model, RuleDefinition.EVENT_FIELD_EXPRESSION,
				"int", null, ".31"); // node's ratio
		RuleDefinition rd32 = new EventFieldDefinition(model, RuleDefinition.EVENT_FIELD_EXPRESSION,
				"int", null, ".32"); // node's minRatio
		
		CompositeDefinition rd3LTO = new CompositeDefinition(RuleDefinition.LESSER_THAN_OPERATOR, null);
		rd3LTO.addOperand(rd31);
		rd3LTO.addOperand(rd32);
		CompositeDefinition rd3LNO = new CompositeDefinition(RuleDefinition.LOGICAL_NOT_OPERATOR, null);
		rd3LNO.addOperand(rd3LTO);
		
		newPolicy3.addExpression(rd3LNO, null, 0);
		newPolicy3.setAuthorization(rd3LNO);
		model.addPolicy(newPolicy3);		
		u1 = new User(1,"u1");
		u1.addModel(model1);
	}
/*	
	private EventObj getIncomingTaskEvent(Task message) {
		log.write(this, "getIncomingTaskEvent called (message: " + message + ")", Log.FINER);

		Vector<RawEventField> allFields = new Vector<RawEventField>();
		allFields.add(new RawEventField(".user.name", "pgama"));
		allFields.add(new RawEventField(".user.company.name", "INESC-ID"));
		allFields.add(new RawEventField(".action.name", action));
		allFields.add(new RawEventField(".action.value", new Integer(1)));
		allFields.add(new RawEventField(".action.parameter[0]", "BEGIN"));
		allFields.add(new RawEventField(".executionTime", 
				new Long(TimeUtilities.getCurrentTime())));
				
		Vector<RawEventField> allFields = new Vector<RawEventField>();
		allFields.add(new RawEventField(".user.name","sesteves"));
		return new EventObj(100, allFields, true,
				0, MainConsole.MODEL_TAG);	
	}
*/	
	private EventObj getIncomingAvailabilityMsgEvent(double ratio) {
		Vector<EventField> v = new Vector<EventField>();
		v.add(new EventField(0, "u0"));
		if(lboundHour < 0) {
			v.add(new EventField(1, 0)); // lboundHour
			v.add(new EventField(2, 2)); //uboundHour		
			v.add(new EventField(3, 1)); // currentHour			
		} else {
			v.add(new EventField(1, lboundHour)); // lboundHour
			v.add(new EventField(2, uboundHour)); //uboundHour
			int currentHour = Integer.parseInt(TimeUtilities.getFormattedCurrentHour().split(":")[0]);		
			v.add(new EventField(3, currentHour)); // currentHour
		}
		v.add(new EventField(21, maxConcurrentTasks)); // maxConcurrentTasks
		v.add(new EventField(22, gridP2P.getNumTasksInProgress())); // tasks running
		v.add(new EventField(31,(int)(ratio*100)));
		v.add(new EventField(32,(int)(minRatio*100)));
		
		return new EventObj(0, v);
	}	
	
	private EventObj getNodeCheckEvent(double minRatio) {
		log.write(this, "getNodeCheckEvent called (minRatio: " + minRatio + ")", Log.FINER);
		
		Vector<EventField> v = new Vector<EventField>();
		v.add(new EventField(0, "u1"));
		// node's ratio, heimdall does not accept double yet
		v.add(new EventField(31, (int)(ratio*100))); // ratio
		v.add(new EventField(32, (int)(minRatio*100))); // minRatio
						
		return new EventObj(1, v);		
	}
	
	private void processEvent(EventObj event) {
		User currentUser = event.getId() == 0 ? u0 : u1;
		server1.registerProcessEventRequest(currentUser, MainConsole.MODEL_TAG, event);
		synchronized (server1) {
			server1.notify();
			// wait for the process to be done
			try {
				server1.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public boolean controlInbound(Message message) {
		log.write(this, "controlInbound called (message: " + message + ")", Log.FINER);

		switch(((RawMessage)message).getType()) {
		case TaskResult.TYPE:			
			outwork += ((TaskResult)message).getWorkDone();
			ratio = inwork/outwork;
			break;
		case Task.TYPE: {
			/*
			EventObj event = getIncomingTaskEvent((Task)message);
			processEvent(event);
			MainConsole.insertEvent(event);
			*/
			// FIXME 
			break;
		}
		case AvailabilityMsg.TYPE: {
											
			EventObj event = getIncomingAvailabilityMsgEvent(((AvailabilityMsg)message).getRatio());
			processEvent(event);
			
			if(server1.eventsInLease.isEmpty()) {
				log.write(this, "Access denied!", Log.FINE);
				return false;
			}
			/*
			for(ModelInstance mi : server1.eventsInLease)
				if(!"FINAL/AUTHORIZE".equals(mi.getDecision().toString())){
					server1.eventsInLease.removeAllElements();										
					return false;
				}
			*/
			log.write(this, "Access granted!", Log.FINE);
			
			server1.eventsInLease.removeAllElements();
			break;
		}
		}
		
		log.write(this,"controlInbound end", Log.FINER);
		return isIdle;
	}
	
	public boolean precontrolOutbound(NodeMetrics nm) {
		log.write(this, "precontrolOutbound called (nm: " + nm.toString() + ")", Log.FINER);
		
		double minRatio = nm.getMinRatio();
		EventObj event = getNodeCheckEvent(minRatio);
		processEvent(event);
		if(server1.eventsInLease.isEmpty()) {
			log.write(this, "Access denied!", Log.FINE);
			return false;
		}
		server1.eventsInLease.removeAllElements();
		log.write(this, "Access granted!", Log.FINE);
		return true;
	}
	
	public boolean controlOutbound(Message message) {
		log.write(this, "controlOuboud called (message: " + message.toString() + ")", Log.FINER);
		
		switch(((RawMessage)message).getType()) {
		case TaskResult.TYPE:
			inwork += ((TaskResult)message).getWorkDone();
			ratio = inwork/outwork;
			break;
		case Task.TYPE:
			((Task)message).setRatio(ratio);
			break;
		case AvailabilityMsg.TYPE:
			((AvailabilityMsg)message).setRatio(ratio);
			break;
		case UpdateMsg.TYPE:
			((UpdateMsg)message).setMinRatio(minRatio);
			break;
		}
		
		log.write(this, "controlOutbound end", Log.FINER);
		return true;
	}
	
	public int getLboundHour() {
		return lboundHour;
	}
	public int getUboundHour() {
		return uboundHour;
	}
	public double getMinRatio() {
		return minRatio;
	}
	public double getRatio() {
		return ratio;
	}
	public void setLboundHour(int hour) {
		lboundHour = hour;
	}
	public void setUboundHour(int hour) {
		uboundHour = hour;
	}
	public void setIsIdle(boolean isIdle) {
		this.isIdle = isIdle;
	}
	public void setMinRatio(double minRatio) {
		this.minRatio = minRatio;
	}
	public void setRatio(double ratio) {
		this.ratio = ratio;
	}
}
